#include "Basic/GxLibBasic.h"
#include "Math.h"
#include "../RayTracePro/Shader.h"

namespace CpFinal
{
	using namespace RayTracePro;
	class BackgroundShadingNode : public ShadingNode
	{
	private:
		ShadingNodeParameter diffuse;
		RayTraceSystem * system;
	public:
		virtual void SimplifiedShade(GxLibMath::Vec4 * result, DifferentialGeometry * dgeom, const Ray * ray, const RayDifferential * rayDiff, Scene * scene, int depth)
		{
			Shade(result, dgeom, ray, rayDiff, scene, depth);
		}
		virtual void Init(RayTraceSystem * system)
		{
			this->system = system;
		}
		virtual char* GetCategory()
		{
			return "Material";
		}
		virtual ShadingNodeReturnType GetReturnType()
		{
			return ShadingNode::Float4;
		}
		virtual int GetParameterCount()
		{
			return 1;
		}
		virtual char* GetParameterName(int i)
		{
			return "Diffuse";
		}
		virtual void GetParameterType(ParameterType * rs, int i)
		{
			rs->EnumValueCount = 0;
			rs->BaseType = ParameterType::ParameterTypeFloat4;
		}
		virtual bool SetParameter(const wchar_t * param, const wchar_t * value)
		{
			return false;
		}
		virtual bool SetParameter(const wchar_t * param, const GxLibMath::Vec4 & value)
		{
			diffuse.Set(value);
			return true;
		}
		virtual bool SetParameter(const wchar_t * param, ShadingNode * shader)
		{
			diffuse.Set(shader);
			return true;
		}
		virtual bool GetParameter(const wchar_t * param, const wchar_t ** value)
		{
			return false;
		}
		virtual bool GetParameter(const wchar_t * param, ShadingNodeParameter & rs)
		{
			rs = diffuse;
			return true;
		}
		bool LightInShadow(Vec3 & l, RayTraceSystem * system, Scene * scene, Light * light, const Vec3 & p)
		{
			if (light->CastShadow)
			{
				Vec3::Subtract(l, light->Position, p);
				float dist = l.Length();
				float t;
				Ray shadowRay;
				if (!light->IsDirectional)
				{
					shadowRay.Origin = p;
					shadowRay.Direction = l;
					shadowRay.ReciprocalDirection.x = 1.0f / shadowRay.Direction.x;
					shadowRay.ReciprocalDirection.y = 1.0f / shadowRay.Direction.y;
					shadowRay.ReciprocalDirection.z = 1.0f / shadowRay.Direction.z;
					shadowRay.tMin = system->ShadowBias;
					shadowRay.tMax = dist;
				}
				else
				{
					shadowRay.Origin = p;
					shadowRay.Direction = Vec3(-light->Direction.x, -light->Direction.y, -light->Direction.z);
					shadowRay.ReciprocalDirection.x = 1.0f / shadowRay.Direction.x;
					shadowRay.ReciprocalDirection.y = 1.0f / shadowRay.Direction.y;
					shadowRay.ReciprocalDirection.z = 1.0f / shadowRay.Direction.z;
					shadowRay.tMin = system->ShadowBias;
					shadowRay.tMax = FLT_MAX;
				}
				if (scene->TraceRayPredCore(t, shadowRay))
					return true;
			}
			return false;
		}
		bool EvalLightRadiance(Vec3 & rs, Vec3 & l, RayTraceSystem * system, Scene * scene, Light * light, const Vec3 & p)
		{
			float decay = 1.0f;
			float dist;
			rs.SetZero();
			if (light->IsDirectional)
			{
				l = light->Direction;
				l.x = -l.x; l.y = -l.y; l.z = -l.z;
			}
			else
			{
				Vec3::Subtract(l, light->Position, p);
				dist = l.Length();
				Vec3::Scale(l, l, 1.0f/dist);
				if (light->Cutoff)
				{
					float dot = -Vec3::Dot(l, light->Direction);
					if (dot < light->FullCutoffCos)
					{
						return false;
					}
					else if (dot < light->StartCutoffCos)
					{
						float ang = acos(dot);
						if (dot > light->HalfCutoffAngle)
						{
							float t = (ang - light->StartCutoffAngle) * light->RcpHalfMinusStart;
							decay = 1.0f - t*0.5f;
						}
						else
						{
							float t = (ang - light->HalfCutoffAngle) * light->RcpFullMinusHalf;
							decay = (1.0f - t) * 0.5f;
						}
					}
				}
				if (light->Decay == Light::Linear)
				{
					decay *= 1.0f / Min(1.0f, dist * light->DecayFactor);
				}
				else if (light->Decay == Light::Quadric)
				{
					decay *= 1.0f / Max(1.0f, dist * dist * light->DecayFactor);
				}
				/*if (decay < 0.001f)
				{
					return false;
				}*/
			}
			Vec3 tmp;
			Vec3::Scale(tmp, light->Intensity, decay);
			rs = tmp;
			return true;
		}
		virtual void Shade(GxLibMath::Vec4 * result, DifferentialGeometry * dgeom, const Ray * ray, const RayDifferential * rayDiff, Scene * scene, int depth)
		{
			Vec4 & rs = *result;
			rs = system->AmbientLight;
			rs.w = 1.0f;
			Vec4 diffuseVal;
			diffuse.Get(&diffuseVal, dgeom, ray, rayDiff, scene, depth);
			Light ** lights;
			int lightCount;
			scene->GetLights(dgeom->Position, lights, lightCount);
			Vec3 sum_rad = system->AmbientLight;
			for (int i = 0; i < lightCount; i++)
			{
				Vec3 rad, l;
				Vec3 pos, tmp;
				Vec3::Scale(tmp, dgeom->Normal, system->ShadowBias);
				Vec3::Add(pos, dgeom->Position, tmp);
				EvalLightRadiance(rad, l, system, scene, lights[i], pos);
				float lambert = Max(0.0f, Vec3::Dot(l, dgeom->Normal));
				Vec3::Scale(rad, rad, lambert);
				Vec3::Add(sum_rad, sum_rad, rad);
				if (!LightInShadow(l, system, scene, lights[i], pos))
				{
					rs.x += rad.x; rs.y += rad.y; rs.z += rad.z;
				}
			}
			rs.x /= sum_rad.x;
			rs.y /= sum_rad.y;
			rs.z /= sum_rad.z;
			rs.x *= diffuseVal.x; rs.y *= diffuseVal.y; rs.z *= diffuseVal.z;
		}
		virtual ~BackgroundShadingNode()
		{
			
		}
	};
}